// QuicksView.cpp : implementation of the CQuicksView class
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include <sstream>
#include <windows.h>

#include "QuicksView.h"
#include "MainFrm.h"


CQuicksView::CQuicksView(CMainFrame* frame) 
	: m_frame(frame),
	m_totalLength(0), 
	m_resultsLength(0), 
	m_init(false),
	m_textCaretPos(0),
	m_popHeight(0),
	m_maxLines(3) {
		CDC dc(::GetDC(m_hWnd));
		m_lfHeight = -MulDiv(10, GetDeviceCaps(dc, LOGPIXELSY), 72);
		m_hfRegular = CreateFont(m_lfHeight, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, 0, 0, _TEXT("Consolas"));
		m_hfBold = CreateFont(m_lfHeight, 0, 0, 0, FW_HEAVY, 0, 0, 0, 0, 0, 0, 0, 0, _TEXT("Consolas"));
		LOG("Log started");
}

int CQuicksView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	SetMsgHandled(false);
	RegisterHotKey(m_hWnd, 100, MOD_WIN, 'C');
	Clear();
	QueryWindows();
	RefreshResults();
	::ShowWindow(GetParent(), SW_HIDE);
	return 0;
}


void CQuicksView::OnDestroy() {
	UnregisterHotKey(m_hWnd, 100);
}

void CQuicksView::OnHotKey(int nHotKeyID, UINT uModifiers, UINT uVirtKey) {
	Clear();
	QueryWindows();
	RefreshResults();
	::ShowWindow(GetParent(), SW_SHOW);
	::SetForegroundWindow(GetParent());
	SwitchToThisWindow(m_hWnd, true);
}


BOOL CQuicksView::PreTranslateMessage(MSG* pMsg)
{
	pMsg;
	return FALSE;
}

void CQuicksView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	SetMsgHandled(false);
	std::wstringstream ss;
	TCHAR c = (TCHAR)nChar;
	bool bQueueAutoSwitch = false;
	bool bHandled = false;
	switch(nChar)
	{
	case VK_RETURN:
		if (m_matchResult.matches.size() > 0) {
			m_wm.SwitchTo(*m_matchResult.matches[0].data);
			::ShowWindow(GetParent(), SW_HIDE);
			Clear();
			bHandled = true;
		}
		break;
	case VK_BACK:
		if (m_textCaretPos > 0) {
			m_textCaretPos -= nRepCnt;
			m_text.erase(m_textCaretPos - nRepCnt + 1, nRepCnt);
		}
		break;
	case VK_ESCAPE:
		if (m_text.size() == 0)
			::ShowWindow(GetParent(), SW_HIDE);
		Clear();
		break;
	case VK_TAB:
		if (m_matchResult.hint.size() > 0) {
			m_text += m_matchResult.hint;
			m_textCaretPos = m_text.size();
			bHandled = true;
		}
		break;
	default:
		m_text.insert(m_textCaretPos, &c, nRepCnt);
		m_textCaretPos += nRepCnt;
		break;
	}
	RefreshResults();
	if (bQueueAutoSwitch) {
		if (m_matchResult.matches.size() == 1) {
			m_wm.SwitchTo(*m_matchResult.matches[0].data);
			::ShowWindow(GetParent(), SW_HIDE);
			Clear();
		}
	}
	Invalidate();
	SetMsgHandled(bHandled);
}

void CQuicksView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	SetMsgHandled(false);
	switch(nChar)
	{
	case VK_BACK:
		break;
	case VK_DELETE:
		ForwardDelete(nRepCnt);
		break;
	case VK_RETURN:
		break;
	case VK_TAB:
		break;
	default:
		break;
	}
}

void CQuicksView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	SetMsgHandled(false);
	switch(nChar)
	{
	case VK_BACK:
		break;
	case VK_DELETE:
		break;
	case VK_RETURN:
		break;
	case VK_TAB:
		break;
	default:
		break;
	}
}

void CQuicksView::QueryWindows() {
	m_windows.clear();
	const std::vector<WindowManager::WindowDesc>* windows = m_wm.GetWindows();
	MatchOutput<WindowManager::WindowDesc> mo;
	m_shownWindows.clear();
	for (std::vector<WindowManager::WindowDesc>::const_iterator it = windows->begin(); it != windows->end(); it++) {
		m_shownWindows.push_back(*it);
		MatchInput<WindowManager::WindowDesc> mi;
		mi.str = &(it->name);
		mi.data = &(*it); // no pointers into std!
		m_windows.push_back(mi);
	}
}
	
void CQuicksView::RefreshResults() {
	m_ic.MatchInput(m_matchResult, m_text, m_windows);
}

void CQuicksView::Clear() {
	m_matchResult.matches.clear();
	m_text.clear();
	m_textCaretPos = 0;
	m_popHeight = 0;
}

LRESULT CQuicksView::OnNextMatch(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
	if (m_matchResult.matches.size() < 2)
		return 0;
	IdoComplete<WindowManager::WindowDesc>::Match m;
	IdoComplete<WindowManager::WindowDesc>::MatchVector::iterator fst = m_matchResult.matches.begin();
	m = *fst;
	m_matchResult.matches.erase(fst);
	m_matchResult.matches.push_back(m);
	Invalidate();
	bHandled = true;
	return 0;
}

LRESULT CQuicksView::OnPrevMatch(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
	if (m_matchResult.matches.size() < 2)
		return 0;
	IdoComplete<WindowManager::WindowDesc>::Match m;
	IdoComplete<WindowManager::WindowDesc>::MatchVector::iterator lst = m_matchResult.matches.end() - 1;
	m = *lst;
	m_matchResult.matches.erase(lst);
	m_matchResult.matches.insert(m_matchResult.matches.begin(), m);
	Invalidate();
	bHandled = true;
	return 0;
}
LRESULT CQuicksView::OnNextChar(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	if (m_textCaretPos < m_text.size()) {
		m_textCaretPos++;
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnPrevChar(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	if (m_textCaretPos > 0) {
		m_textCaretPos--;
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnNextWord(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	if (m_textCaretPos < m_text.size()) {
		m_textCaretPos++;
		while(m_textCaretPos < m_text.size() && m_text[m_textCaretPos] != _T(' ')) {
			m_textCaretPos++;
		}
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnPrevWord(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	if (m_textCaretPos > 0) {
		m_textCaretPos--;
		while(m_textCaretPos > 0 && m_text[m_textCaretPos - 1] != _T(' ')) {
			m_textCaretPos--;

		}
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnFirst(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	if (m_textCaretPos != 0) {
		m_textCaretPos = 0;
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnLast(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	if (m_textCaretPos != m_text.size()) {
		m_textCaretPos = m_text.size();
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnKillLine(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	
	if (m_textCaretPos != m_text.size()) {
		m_text.erase(m_textCaretPos, m_text.size() - m_textCaretPos);
		RefreshResults();
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnKillWord(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	int toKill = 0;
	if (m_textCaretPos > 0) {
		m_textCaretPos--;
		toKill++;
		while(m_textCaretPos > 0 && m_text[m_textCaretPos - 1] != _T(' ')) {
			m_textCaretPos--;
			toKill++;
		}
		m_text.erase(m_textCaretPos, toKill);
		RefreshResults();
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnKillWordForward(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	int toKill = 0;
	if (m_textCaretPos < m_text.size()) {
		toKill++;
		while(toKill + m_textCaretPos < m_text.size() && m_text[toKill + m_textCaretPos] != _T(' ')) {
			toKill++;
		}
		m_text.erase(m_textCaretPos, toKill);
		RefreshResults();
		Invalidate();
	}
	return 0;
}
LRESULT CQuicksView::OnKillCharForward(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
	ForwardDelete(1);
	return 0;
}

void CQuicksView::ForwardDelete(int len) {
	if (m_textCaretPos < m_text.size()) {
		m_text.erase(m_textCaretPos, len);
		RefreshResults();
		Invalidate();
	}
}
LRESULT CQuicksView::OnRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	QueryWindows();
	bHandled = TRUE;
	return 0;
}
LRESULT CQuicksView::OnShow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	Clear();
	QueryWindows();
	RefreshResults();
	::ShowWindow(GetParent(), SW_SHOW);
	::SetForegroundWindow(GetParent());
	SwitchToThisWindow(m_hWnd, true);
	return 0;
}

void CQuicksView::OnPaint(CDCHandle dc) {
	CPaintDC pdc(m_hWnd);

	// colors... TODO promote.
	COLORREF crBkgrd = RGB(0,0,0);
	COLORREF crMatching = RGB(255,105,180);
	COLORREF crHint = RGB(0,191,255);
	COLORREF crDefault = RGB(255,255,255);
	COLORREF crNoMatches = RGB(255,69,0);
	COLORREF crExactMatch = RGB(0,238,0);

	RECT r;
	RECT rCaret;
	GetClientRect(&r);
	rCaret = r;
	rCaret.left = 0;
	rCaret.right = 6;
	rCaret.top = 0;
	rCaret.bottom = -m_lfHeight + 2;
	pdc.FillSolidRect(&r, crBkgrd);
	pdc.SetBkColor(crBkgrd);
	pdc.SelectFont(m_hfRegular);
	pdc.SetTextColor(crDefault);
	int height = -m_lfHeight + 2;

	// draw 0..m_textCaretPos
	DrawStringAndMeasure(pdc, m_text.c_str(), m_textCaretPos, r);
	rCaret.left = r.left;

	if (m_textCaretPos < m_text.size()) {
		// draw m_textCaretPos
		DrawStringAndMeasure(pdc, m_text.c_str() + m_textCaretPos, 1, r);
		rCaret.right = r.left;
		
		// draw m_textCaretPos + 1..m_text.size()
		DrawStringAndMeasure(pdc, m_text.c_str() + m_textCaretPos + 1, 
									  m_text.size() - (m_textCaretPos + 1), r);

	} else {
		// need to draw the caret on the next char
		rCaret.left = r.left;
		rCaret.right = r.left + 6;
	}

	// draw the results...
	if (m_matchResult.hint.size() > 0 && m_matchResult.matches.size() > 1) {
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT("["), 1, r);
		pdc.SetTextColor(crHint);
		DrawStringAndMeasure(pdc, m_matchResult.hint, r);
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT("]"), 1, r);
	}
	if (m_matchResult.matches.size() == 0) {
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT(" ["), 2, r);
		pdc.SetTextColor(crNoMatches);
		DrawStringAndMeasure(pdc, _TEXT("No Match"), 8, r);
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT("]"), 1, r);
	} else if (m_matchResult.matches.size() == 1) {
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT("["), 1, r);
		pdc.SetTextColor(crExactMatch);
		wstring curStr;
		MatchStringToWString(m_matchResult.matches.front().str, curStr);
		DrawStringAndMeasure(pdc, curStr, r);
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT("]"), 1, r);
	} else {
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT("{"), 1, r);

		bool bMatching = false;
		TCHAR buf[IdoComplete<WindowManager::WindowDesc>::MAXSIZEX];
		for (IdoComplete<WindowManager::WindowDesc>::MatchVector::const_iterator it = 
			m_matchResult.matches.begin(); it != m_matchResult.matches.end(); it++) {

				if (it == m_matchResult.matches.begin()) {
					pdc.SelectFont(m_hfBold);
				} else {
					pdc.SelectFont(m_hfRegular);
				}
				int len = 0;
				for (matchstring::const_iterator it2 = it->str.begin(); it2 != it->str.end(); it2++) {
					if (bMatching != it2->matches) {
						if (bMatching) {
							pdc.SetTextColor(crMatching);
						} else {
							pdc.SetTextColor(crDefault);
						}
						DrawStringAndMeasure(pdc, buf, len, r);
						bMatching = it2->matches;
						len = 0;
					}
					buf[len] = it2->c;
					len++;
				}
				// set the remaining
				if (bMatching) {
					pdc.SetTextColor(crMatching);
				} else {
					pdc.SetTextColor(crDefault);
				}
				DrawStringAndMeasure(pdc, buf, len, r);
				
				// divider
				if (it != m_matchResult.matches.end() - 1) {
					pdc.SelectFont(m_hfRegular);
					pdc.SetTextColor(crDefault);
					DrawStringAndMeasure(pdc, _TEXT(" | "), 3, r);
					bMatching = false;
				}
		}
		pdc.SetTextColor(crDefault);
		DrawStringAndMeasure(pdc, _TEXT("}"), 1, r);
	}

	// color the caret.
	pdc.InvertRect(&rCaret);
	RECT rWindow;
	m_frame->GetWindowRect(&rWindow);
	m_popHeight = max(m_popHeight, r.top);
	rWindow.top = rWindow.bottom - height - m_popHeight;
	m_frame->SetWindowPos(HWND_TOP, &rWindow, SWP_NOZORDER);
}

void CQuicksView::OnKillFocus(CWindow wndFocus) {
	::ShowWindow(GetParent(), SW_HIDE);
	Clear();
}

int CQuicksView::DrawStringAndMeasure(CPaintDC& pdc, wstring str, RECT& r) {
	return DrawStringAndMeasure(pdc, str.c_str(), str.size(), r);
}
int CQuicksView::DrawStringAndMeasure(CPaintDC& pdc, LPCTSTR str, int len, RECT& r) {
	int nDrawn = 0;
	int nLastLeft = 0;
	int nFit;
	SIZE size;
	int lines = 0;
	// no word wrapping for now
	
	while (nDrawn != len) {
		pdc.GetTextExtentExPointW(str + nDrawn, len - nDrawn, &size, r.right - r.left, &nFit, m_textWidths);
		pdc.DrawTextW(str + nDrawn, nFit, &r, NULL);
		nDrawn += nFit;
		r.left += nFit > 0? m_textWidths[nFit - 1] : 0;
		if(nDrawn < len) {
			r.top += -m_lfHeight + 2;
			r.left = nLastLeft;
			lines++;
		}
	}
	return lines;
}

HBRUSH CQuicksView::OnCtlColorStatic(CDCHandle dc, CStatic wndStatic) {
	dc.SetBkMode(TRANSPARENT);
	return (HBRUSH)GetStockObject(NULL_BRUSH);
}

BOOL CQuicksView::OnEraseBkgnd(CDCHandle dc) {
	return true;
}